﻿// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Exceptions;
using ValidationAspects.Factories;
using ValidationAspects.Sdk;
using ValidationAspects.Unity;

namespace TestValidationAspects.Unity
{
	[TestFixture]
	public class TestInterfaceInterception
	{
		private IUnityContainer _container;

		[SetUp]
		public void SetUp()
		{
			TestHelpers.ResetFramework();

			_container = new UnityContainer();
			_container.AddNewExtension<Interception>();
		}

		#region Test Intercept Properties
		[Test]
		public void Test_InterfaceInterception_for_all_properties()
		{
			_container.RegisterType<IDummy, Dummy>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).AddCallHandler(
				new ValidationCallHandler());

			TypeOf<Dummy>.GetProperty(x => x.PropertyB).AddValidation(new[] {new NotNullOrEmpty()});

			IDummy idummy = _container.Resolve<IDummy>();
			Test.Throws<ValidateNotNullOrEmptyException>(() => idummy.AttributedProperty = null);
			Test.Throws<ValidateNotNullOrEmptyException>(() => idummy.PropertyB = null);

			Dummy dummy = _container.Resolve<Dummy>();
			dummy.AttributedProperty = null;
			dummy.PropertyB = null;
		}

		[Test]
		public void Test_InterfaceInterception_for_attributed_properties_only()
		{
			_container.RegisterType<IDummy, Dummy>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule
			                                                                                                                                                       {AttributedPropertiesOnly = true}).
				AddCallHandler(new ValidationCallHandler());

			TypeOf<Dummy>.GetProperty(x => x.PropertyB).AddValidation(new[] {new NotNullOrEmpty()});

			IDummy idummy = _container.Resolve<IDummy>();

			Test.Throws<ValidateNotNullOrEmptyException>(() => idummy.AttributedProperty = null);
			idummy.PropertyB = null;

			Dummy dummy = _container.Resolve<Dummy>();
			dummy.AttributedProperty = null;
			dummy.PropertyB = null;
		}
		#endregion

		#region Test Intercept Methods
		[Test]
		public void Test_InterfaceInterception_for_all_methods()
		{
			_container.RegisterType<IDummy, Dummy>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).AddCallHandler(
				new ValidationCallHandler());

			typeof(Dummy).GetMethod("MethodB").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});

			IDummy idummy = _container.Resolve<IDummy>();

			Test.Throws<ValidationContextException>(() => idummy.AttributedMethod(null));
			Test.Throws<ValidationContextException>(() => idummy.MethodB(null));

			Dummy dummy = _container.Resolve<Dummy>();
			dummy.AttributedMethod(null);
			dummy.MethodB(null);
		}

		[Test]
		public void Test_InterfaceInterception_for_attributed_methods_only()
		{
			_container.RegisterType<IDummy, Dummy>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule
			                                                                                                                                                       {AttributedMethodsOnly = true}).AddCallHandler
				(new ValidationCallHandler());

			typeof(Dummy).GetMethod("MethodB").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});

			IDummy idummy = _container.Resolve<IDummy>();

			Test.Throws<ValidationContextException>(() => idummy.AttributedMethod(null));
			idummy.MethodB(null);

			Dummy dummy = _container.Resolve<Dummy>();
			dummy.AttributedMethod(null);
			dummy.MethodB(null);
		}
		#endregion

		#region Test Generic Types
		[Test]
		public void Test_generic_class_is_object_validatable()
		{
			_container.RegisterType(typeof(IGenericDummy<>), typeof(GenericDummy<>));

			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(IGenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule())
				.AddCallHandler(new ValidationCallHandler());

			IGenericDummy<string> idummy = (IGenericDummy<string>)_container.Resolve(typeof(IGenericDummy<string>));

			idummy.DoIt("s"); // succeeds

			idummy.ReportObjectIsInvalid = true;

			try
			{
				idummy.DoIt("s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateObjectException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_parameter_validatable()
		{
			_container.RegisterType(typeof(IGenericDummy<>), typeof(GenericDummy<>));

			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(IGenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule())
				.AddCallHandler(new ValidationCallHandler());

			IGenericDummy<string> idummy = (IGenericDummy<string>)_container.Resolve(typeof(IGenericDummy<string>));

			try
			{
				idummy.DoIt(null);
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_non_generic_property_validatable()
		{
			_container.RegisterType(typeof(IGenericDummy<>), typeof(GenericDummy<>));

			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(IGenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule())
				.AddCallHandler(new ValidationCallHandler());

			IGenericDummy<string> idummy = (IGenericDummy<string>)_container.Resolve(typeof(IGenericDummy<string>));
			idummy.StringProperty = "s";
			Test.Throws<ValidateNotEqualException>(() => idummy.StringProperty = "none");
		}

		[Test]
		public void Test_generic_class_is_generic_property_validatable()
		{
			_container.RegisterType(typeof(IGenericDummy<>), typeof(GenericDummy<>));

			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(IGenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule())
				.AddCallHandler(new ValidationCallHandler());

			IGenericDummy<string> idummy = (IGenericDummy<string>)_container.Resolve(typeof(IGenericDummy<string>));
			idummy.GenericProperty = "s";
			Test.Throws<ValidateNotEqualException>(() => idummy.GenericProperty = "none");
		}
		#endregion

		#region Test Validator Attribute declaration
		[Test]
		[ExpectedException(typeof(ValidationException))]
		public void Test_method_invocation_throws_for_invalid_validation_decoration_method()
		{
			_container.RegisterType<IInvalidValidatorDecorationMethod, InvalidValidatorDecorationMethod>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IInvalidValidatorDecorationMethod>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(
				new ValidationMatchingRule()).AddCallHandler(new ValidationCallHandler());

			_container.Resolve<IInvalidValidatorDecorationMethod>().DoIt();
		}

		[Test]
		[ExpectedException(typeof(ValidationException))]
		public void Test_method_invocation_throws_for_invalid_validation_decoration_parameter()
		{
			_container.RegisterType<IInvalidValidatorDecorationParameter, InvalidValidatorDecorationParameter>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IInvalidValidatorDecorationParameter>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(
				new ValidationMatchingRule()).AddCallHandler(new ValidationCallHandler());

			_container.Resolve<IInvalidValidatorDecorationParameter>().DoIt("test");
		}

		[Test]
		[ExpectedException(typeof(ValidationException))]
		public void Test_property_invocation_throws_for_invalid_validation_decoration_property()
		{
			_container.RegisterType<IInvalidValidatorDecorationProperty, InvalidValidatorDecorationProperty>();
			_container.Configure<Interception>().SetDefaultInterceptorFor<IInvalidValidatorDecorationProperty>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(
				new ValidationMatchingRule()).AddCallHandler(new ValidationCallHandler());

			_container.Resolve<IInvalidValidatorDecorationProperty>().Property = "test";
		}
		#endregion

		#region Mocks
		public interface IDummy
		{
			string AttributedProperty { get; set; }

			string PropertyB { get; set; }

			void AttributedMethod(string s);

			void MethodB(string s);
		}

		public class Dummy : IDummy
		{
			#region Implementation of IDummy
			[NotNullOrEmpty]
			public string AttributedProperty { get; set; }

			public string PropertyB { get; set; }

			public void AttributedMethod([NotNullOrEmpty] string s) { }

			public void MethodB(string s) { }
			#endregion
		}

		public interface IGenericDummy<T>
		{
			bool ReportObjectIsInvalid { get; set; }
			void DoIt(string s);
			string StringProperty { get; set; }
			T GenericProperty { get; set; }
		}

		public class GenericDummy<T> : IGenericDummy<T>
		{
			public bool ReportObjectIsInvalid { get; set; }

			[ValidateObject]
			public void DoIt([NotNullOrEmpty] string s) { }

			[NotEqual("none")]
			public string StringProperty { get; set; }

			[NotEqual("none")]
			public T GenericProperty { get; set; }

			[ValidationMethod]
			public static void ValidateDummy(GenericDummy<T> genericDummy)
			{
				if (genericDummy.ReportObjectIsInvalid)
					throw new ValidationException("invalid dummy");
			}
		}

		[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
		public class SpecificTypeValidatorAttribute : ValidatorAttribute
		{
			#region Overrides of ValidatorAttribute
			public override IValidatorFactory Factory
			{
				get { return new InlineValidatorFactory<Dummy>((v, c) => { }); }
			}
			#endregion
		}

		public interface IInvalidValidatorDecorationMethod
		{
			void DoIt();
		}

		public class InvalidValidatorDecorationMethod : IInvalidValidatorDecorationMethod
		{
			[SpecificTypeValidator]
			public void DoIt() { }
		}

		public interface IInvalidValidatorDecorationParameter
		{
			void DoIt(string s);
		}

		public class InvalidValidatorDecorationParameter : IInvalidValidatorDecorationParameter
		{
			public void DoIt([LessThan(100)] string s) { }
		}

		public interface IInvalidValidatorDecorationProperty
		{
			string Property { get; set; }
		}

		public class InvalidValidatorDecorationProperty : IInvalidValidatorDecorationProperty
		{
			[NotEqual(123.456)]
			public string Property { get; set; }
		}
		#endregion
	}
}